﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using Carson.DataAccess.Attributes;
using Carson.DataAccess.PagePlugin;
using IBatisNet.Common.Utilities.Objects;
using IBatisNet.DataMapper;
using IBatisNet.DataMapper.Configuration.ParameterMapping;
using IBatisNet.DataMapper.Configuration.Statements;
using IBatisNet.DataMapper.MappedStatements;
using IBatisNet.DataMapper.MappedStatements.PostSelectStrategy;
using IBatisNet.DataMapper.MappedStatements.ResultStrategy;
using IBatisNet.DataMapper.Scope;
using log4net;

namespace Carson.DataAccess
{
    public class IBatisExt
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(IBatisExt));

        #region QueryForPageList
        public static IList QueryForPageList(ISqlMapper sqlMap, String statementName, Object parameter, int startIndex, int count, string sidx, string sord)
        {
            bool isSessionLocal = false;
            ISqlMapSession session = sqlMap.LocalSession;
            IList list;

            if (session == null)
            {
                session = sqlMap.CreateSqlMapSession();
                isSessionLocal = true;
            }

            try
            {
                IMappedStatement statement = sqlMap.GetMappedStatement(statementName);
                RequestScope request = statement.Statement.Sql.GetRequestScope(statement, parameter, session);
                statement.PreparedCommand.Create(request, session, statement.Statement, parameter);
                list = RunQueryForPageList(request, session, parameter, statement.Statement, startIndex, count, sidx, sord);
            }
            finally
            {
                if (isSessionLocal)
                {
                    session.CloseConnection();
                }
            }

            return list;
        }


        public static IList<T> QueryForPageList<T>(ISqlMapper sqlMap, String statementName, Object parameter, int startIndex, int count, string sidx, string sord)
        {
            bool isSessionLocal = false;
            ISqlMapSession session = sqlMap.LocalSession;
            IList<T> list;

            if (session == null)
            {
                session = sqlMap.CreateSqlMapSession();
                isSessionLocal = true;
            }

            try
            {
                IMappedStatement statement = sqlMap.GetMappedStatement(statementName);
                RequestScope request = statement.Statement.Sql.GetRequestScope(statement, parameter, session);
                statement.PreparedCommand.Create(request, session, statement.Statement, parameter);
                list = RunQueryForPageList<T>(request, session, parameter, statement.Statement, startIndex, count, sidx, sord);
            }
            finally
            {
                if (isSessionLocal)
                {
                    session.CloseConnection();
                }
            }

            return list;
        }
        #endregion

        #region QueryForPageTable
        public static DataTable QueryForPageTable(ISqlMapper sqlMap, String statementName, Object parameter, int startIndex, int count, string sidx, string sord)
        {
            bool isSessionLocal = false;
            ISqlMapSession session = sqlMap.LocalSession;
            DataTable dt;

            if (session == null)
            {
                session = sqlMap.CreateSqlMapSession();
                isSessionLocal = true;
            }

            try
            {
                IMappedStatement statement = sqlMap.GetMappedStatement(statementName);
                RequestScope request = statement.Statement.Sql.GetRequestScope(statement, parameter, session);
                statement.PreparedCommand.Create(request, session, statement.Statement, parameter);
                dt = RunQueryForPageTable(request, session, statement.Statement, startIndex, count, sidx, sord);
            }
            finally
            {
                if (isSessionLocal)
                {
                    session.CloseConnection();
                }
            }

            return dt;
        }
        #endregion

        #region QueryCount
        public static int QueryCount(ISqlMapper sqlMap, String statementName, Object parameter)
        {
            bool isSessionLocal = false;
            ISqlMapSession session = sqlMap.LocalSession;
            int result;

            if (session == null)
            {
                session = sqlMap.CreateSqlMapSession();
                isSessionLocal = true;
            }

            try
            {
                IMappedStatement statement = sqlMap.GetMappedStatement(statementName);
                RequestScope request = statement.Statement.Sql.GetRequestScope(statement, parameter, session);
                statement.PreparedCommand.Create(request, session, statement.Statement, parameter);
                result = RunQueryForCount(request);
            }
            finally
            {
                if (isSessionLocal)
                {
                    session.CloseConnection();
                }
            }

            return result;
        }
        #endregion

        #region InsertEntity
        public static int InsertEntity<T>(ISqlMapper sqlMap, T entity)
        {
            bool isSessionLocal = false;
            ISqlMapSession session = sqlMap.LocalSession;

            if (session == null)
            {
                session = sqlMap.CreateSqlMapSession();
                isSessionLocal = true;
            }

            try
            {
                IMappedStatement statement = sqlMap.GetMappedStatement("Carson.InsertEntity");
                RequestScope request = statement.Statement.Sql.GetRequestScope(statement, entity, session);
                statement.PreparedCommand.Create(request, session, statement.Statement, entity);
                return RunInsertEntity<T>(request, session, entity);
            }
            catch (Exception ex)
            {
                log.Error("[InsertEntity异常]:" + ex.Message, ex);
                throw;
            }
            finally
            {
                if (isSessionLocal)
                {
                    session.CloseConnection();
                }
            }
        }
        #endregion

        #region UpdateEntity
        public static int UpdateEntity<T>(ISqlMapper sqlMap, T entity)
        {
            bool isSessionLocal = false;
            ISqlMapSession session = sqlMap.LocalSession;

            if (session == null)
            {
                session = sqlMap.CreateSqlMapSession();
                isSessionLocal = true;
            }

            try
            {
                IMappedStatement statement = sqlMap.GetMappedStatement("Carson.UpdateEntity");
                RequestScope request = statement.Statement.Sql.GetRequestScope(statement, entity, session);
                statement.PreparedCommand.Create(request, session, statement.Statement, entity);
                return RunUpdateEntity<T>(request, session, entity);
            }
            catch (Exception ex)
            {
                log.Error("[UpdateEntity异常]:" + ex.Message, ex);
                throw;
            }
            finally
            {
                if (isSessionLocal)
                {
                    session.CloseConnection();
                }
            }
        }

        #endregion

        #region DeleteEntity
        public static int DeleteEntity<T>(ISqlMapper sqlMap, T entity)
        {
            bool isSessionLocal = false;
            ISqlMapSession session = sqlMap.LocalSession;

            if (session == null)
            {
                session = sqlMap.CreateSqlMapSession();
                isSessionLocal = true;
            }

            try
            {
                IMappedStatement statement = sqlMap.GetMappedStatement("Carson.DeleteEntity");
                RequestScope request = statement.Statement.Sql.GetRequestScope(statement, entity, session);
                statement.PreparedCommand.Create(request, session, statement.Statement, entity);
                return RunDeleteEntity<T>(request, session, entity);
            }
            catch (Exception ex)
            {
                log.Error("[DeleteEntity异常]:" + ex.Message, ex);
                throw;
            }
            finally
            {
                if (isSessionLocal)
                {
                    session.CloseConnection();
                }
            }
        }


        #endregion

        #region DeleteById
        public static int DeleteById<T>(ISqlMapper sqlMap, Object id)
        {
            bool isSessionLocal = false;
            ISqlMapSession session = sqlMap.LocalSession;

            if (session == null)
            {
                session = sqlMap.CreateSqlMapSession();
                isSessionLocal = true;
            }

            try
            {
                IMappedStatement statement = sqlMap.GetMappedStatement("Carson.DeleteById");
                RequestScope request = statement.Statement.Sql.GetRequestScope(statement, id, session);
                statement.PreparedCommand.Create(request, session, statement.Statement, id);
                return RunDeleteById<T>(request, session, id);
            }
            catch (Exception ex)
            {
                log.Error("[DeleteById异常]:" + ex.Message, ex);
                throw;
            }
            finally
            {
                if (isSessionLocal)
                {
                    session.CloseConnection();
                }
            }
        }


        #endregion

        #region private methods
        private static IList RunQueryForPageList(RequestScope request, ISqlMapSession session, object parameterObject, IStatement _statement, int startIndex, int count, string sidx, string sord)
        {
            IList list;
            using (IDbCommand command = request.IDbCommand)
            {
                string limitSql = PagePluginProvider.GetPagePlugin(session.SqlMapper).GetLimitString(command.CommandText, startIndex, count, sidx, sord);
                log.Debug("分页sql：" + limitSql);
                command.CommandText = limitSql;
                list = (_statement.ListClass == null) ? (new ArrayList()) : (_statement.CreateInstanceOfListClass());
                IDataReader reader = command.ExecuteReader();
                try
                {
                    while (reader.Read())
                    {
                        object obj = ResultStrategyFactory.Get(_statement).Process(request, ref reader, null);
                        if (obj != BaseStrategy.SKIP)
                        {
                            list.Add(obj);
                        }
                    }
                }
                finally
                {
                    reader.Close();
                    reader.Dispose();
                }
                ExecutePostSelect(request);
                RetrieveOutputParameters(request, session, command, parameterObject);
            }
            return list;
        }

        private static IList<T> RunQueryForPageList<T>(RequestScope request, ISqlMapSession session, object parameterObject, IStatement _statement, int startIndex, int count, string sidx, string sord)
        {
            IList<T> list;

            using (IDbCommand command = request.IDbCommand)
            {
                string limitSql = PagePluginProvider.GetPagePlugin(session.SqlMapper).GetLimitString(command.CommandText, startIndex, count, sidx, sord);
                log.Debug("分页sql：" + limitSql);
                command.CommandText = limitSql;
                if (_statement.ListClass == null)
                {
                    list = new List<T>();
                }
                else
                {
                    list = _statement.CreateInstanceOfGenericListClass<T>();
                }

                IDataReader reader = command.ExecuteReader();
                try
                {
                    while (reader.Read())
                    {
                        object obj = ResultStrategyFactory.Get(_statement).Process(request, ref reader, null);
                        if (obj != BaseStrategy.SKIP)
                        {
                            list.Add((T)obj);
                        }
                    }
                }
                finally
                {
                    reader.Close();
                    reader.Dispose();
                }

                ExecutePostSelect(request);

                RetrieveOutputParameters(request, session, command, parameterObject);
            }

            return list;
        }

        private static DataTable RunQueryForPageTable(RequestScope request, ISqlMapSession session, IStatement _statement, int startIndex, int count, string sidx, string sord)
        {
            DataTable dataTable;

            using (IDbCommand command = request.IDbCommand)
            {
                string limitSql = PagePluginProvider.GetPagePlugin(session.SqlMapper).GetLimitString(command.CommandText, startIndex, count, sidx, sord);
                log.Debug("分页sql：" + limitSql);
                command.CommandText = limitSql;

                dataTable = new DataTable(_statement.Id);
                dataTable.Load(command.ExecuteReader());
            }

            return dataTable;
        }

        private static int RunQueryForCount(RequestScope request)
        {
            int count;
            using (IDbCommand command = request.IDbCommand)
            {
                string countSql = PagePluginProvider.GetPagePlugin(request.Session.SqlMapper).GetCountString(command.CommandText);
                log.Debug("总记录数sql：" + countSql);
                command.CommandText = countSql;
                count = Convert.ToInt32(command.ExecuteScalar());
            }
            return count;
        }

        private static void ExecutePostSelect(RequestScope request)
        {
            while (request.QueueSelect.Count > 0)
            {
                PostBindind postSelect = request.QueueSelect.Dequeue() as PostBindind;

                PostSelectStrategyFactory.Get(postSelect.Method).Execute(postSelect, request);
            }
        }

        private static void RetrieveOutputParameters(RequestScope request, ISqlMapSession session, IDbCommand command, object result)
        {
            if (request.ParameterMap != null)
            {
                int count = request.ParameterMap.PropertiesList.Count;
                for (int i = 0; i < count; i++)
                {
                    ParameterProperty mapping = request.ParameterMap.GetProperty(i);
                    if (mapping.Direction == ParameterDirection.Output ||
                        mapping.Direction == ParameterDirection.InputOutput)
                    {
                        string parameterName;
                        if (session.DataSource.DbProvider.UseParameterPrefixInParameter == false)
                        {
                            parameterName = mapping.ColumnName;
                        }
                        else
                        {
                            parameterName = session.DataSource.DbProvider.ParameterPrefix +
                                mapping.ColumnName;
                        }

                        if (mapping.TypeHandler == null) // Find the TypeHandler
                        {
                            lock (mapping)
                            {
                                if (mapping.TypeHandler == null)
                                {
                                    Type propertyType = ObjectProbe.GetMemberTypeForGetter(result, mapping.PropertyName);

                                    mapping.TypeHandler = request.DataExchangeFactory.TypeHandlerFactory.GetTypeHandler(propertyType);
                                }
                            }
                        }

                        // Fix IBATISNET-239
                        //"Normalize" System.DBNull parameters
                        IDataParameter dataParameter = (IDataParameter)command.Parameters[parameterName];
                        object dbValue = dataParameter.Value;

                        object value;

                        bool wasNull = (dbValue == DBNull.Value);
                        if (wasNull)
                        {
                            if (mapping.HasNullValue)
                            {
                                value = mapping.TypeHandler.ValueOf(mapping.GetAccessor.MemberType, mapping.NullValue);
                            }
                            else
                            {
                                value = mapping.TypeHandler.NullValue;
                            }
                        }
                        else
                        {
                            value = mapping.TypeHandler.GetDataBaseValue(dataParameter.Value, result.GetType());
                        }

                        request.IsRowDataFound = request.IsRowDataFound || (value != null);

                        request.ParameterMap.SetOutputParameter(ref result, mapping, value);
                    }
                }
            }
        }

        private static int RunInsertEntity<T>(RequestScope request, ISqlMapSession session, Object entity)
        {
            Type tEntity = typeof(T);
            var mapTableAttr = (TableAttribute)tEntity.GetCustomAttributes(typeof(TableAttribute), false)[0];
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("insert into {0} (", mapTableAttr.Name);
            var pros = GetPropertisForColumns(tEntity);
            foreach (PropertyInfo pi in pros)
            {
                sb.Append(pi.Name + ",");
            }
            sb.Remove(sb.Length - 1, 1);
            sb.Append(")values");
            using (IDbCommand command = request.IDbCommand)
            {
                StringBuilder sbParams = new StringBuilder();
                foreach (PropertyInfo pi in pros)
                {
                    string paramName = "@" + pi.Name;
                    sbParams.Append("," + paramName);
                    object value = pi.GetValue(entity, null);
                    var param = session.CreateDataParameter();
                    param.ParameterName = paramName;
                    param.Value = value ?? DBNull.Value;
                    command.Parameters.Add(param);
                }
                sb.AppendFormat("({0})", sbParams.ToString().TrimStart(','));
                command.CommandText = sb.ToString();
                log.Debug("[InsertEntity Sql]:" + command.CommandText);
                return command.ExecuteNonQuery();
            }
        }


        private static int RunUpdateEntity<T>(RequestScope request, ISqlMapSession session, object entity)
        {
            Type tEntity = typeof(T);
            var mapTableAttr = (TableAttribute)tEntity.GetCustomAttributes(typeof(TableAttribute), false)[0];
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("update {0} set ", mapTableAttr.Name);
            var pros = GetPropertisForColumns(tEntity);
            bool isFirstField = true;
            var keyPi = pros.First(x => x.GetCustomAttributes(typeof(IdAttribute), true).Any());
            using (IDbCommand command = request.IDbCommand)
            {
                foreach (PropertyInfo pi in pros)
                {
                    bool isKey = pi == keyPi;
                    if (isKey) continue;
                    object value = pi.GetValue(entity, null);
                    string paramName = "@" + pi.Name;
                    if (isFirstField)
                    {
                        sb.AppendFormat("{0}=" + paramName, pi.Name);
                        isFirstField = false;
                    }
                    else
                    {
                        sb.AppendFormat(",{0}=" + paramName, pi.Name);
                    }
                    var param = session.CreateDataParameter();
                    param.ParameterName = paramName;
                    param.Value = value ?? DBNull.Value;
                    command.Parameters.Add(param);
                }
                sb.AppendFormat(" where {0}=@{0}", keyPi.Name);
                var paramKey = session.CreateDataParameter();
                paramKey.ParameterName = "@" + keyPi.Name;
                paramKey.Value = keyPi.GetValue(entity, null);
                command.Parameters.Add(paramKey);
                command.CommandText = sb.ToString();
                log.Debug("[Update Sql]:" + command.CommandText);
                return command.ExecuteNonQuery();
            }
        }

        private static int RunDeleteEntity<T>(RequestScope request, ISqlMapSession session, object entity)
        {
            Type tEntity = typeof(T);
            var mapTableAttr = (TableAttribute)tEntity.GetCustomAttributes(typeof(TableAttribute), false)[0];
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("delete from {0} ", mapTableAttr.Name);
            var pros = GetPropertisForColumns(tEntity);
            var keyPi = pros.First(x => x.GetCustomAttributes(typeof(IdAttribute), true).Any());
            using (IDbCommand command = request.IDbCommand)
            {
                sb.AppendFormat(" where {0}=@{0}", keyPi.Name);
                var paramKey = session.CreateDataParameter();
                paramKey.ParameterName = "@" + keyPi.Name;
                paramKey.Value = keyPi.GetValue(entity, null);
                command.Parameters.Add(paramKey);
                command.CommandText = sb.ToString();
                log.Debug("[Delete Sql]:" + command.CommandText);
                return command.ExecuteNonQuery();
            }
        }

        private static int RunDeleteById<T>(RequestScope request, ISqlMapSession session, object id)
        {
            Type tEntity = typeof(T);
            var mapTableAttr = (TableAttribute)tEntity.GetCustomAttributes(typeof(TableAttribute), false)[0];
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("delete from {0} ", mapTableAttr.Name);
            var pros = GetPropertisForColumns(tEntity);
            var keyPi = pros.First(x => x.GetCustomAttributes(typeof(IdAttribute), true).Any());
            using (IDbCommand command = request.IDbCommand)
            {
                sb.AppendFormat(" where {0}=@{0}", keyPi.Name);
                var paramKey = session.CreateDataParameter();
                paramKey.ParameterName = "@" + keyPi.Name;
                paramKey.Value = id;
                command.Parameters.Add(paramKey);
                command.CommandText = sb.ToString();
                log.Debug("[Delete Sql]:" + command.CommandText);
                return command.ExecuteNonQuery();
            }
        }

        private static PropertyInfo[] GetPropertisForColumns(Type t)
        {
            var arrP = t.GetProperties();
            var q = from pi in arrP
                    where !pi.GetCustomAttributes(typeof(TransientAttribute), true).Any()
                    select pi;
            return q.ToArray();
        }

        #endregion
    }
}
